/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include <stdio.h>
#include <stdlib.h>
#include <aos/aos.h>
#include <aos/kernel.h>
#include <ipc.h>
#include <csi_core.h>

#include "voice_ai.h"

#define voice_ops(v) v->ai_ops
#define voice_control(v) v->control

#define TAG "VCAI"

struct __voice {
    voice_pcm_param_t       *mic_param;
    voice_pcm_param_t       *ref_param;
    voice_param_t            param;

    voice_cts_ops_t         *ai_ops;
    void                    *priv;
    aos_sem_t                sem;
    ipc_t                   *ipc;
    long long                aec_time_stamp;
    long long                vad_time_stamp;
    long long                asr_time_stamp;
    int                      state;
    int                      fake_wakeup;
    volatile int             task_running;
    voice_data_t             *pcm_data;
    voice_data_t             *backflow_data[VOCIE_BACKFLOW_DATA];
    int                      backflow_enable[VOCIE_BACKFLOW_DATA];

    voice_ringbuf_t         *voice_rb;
    int                      cts_ms; //每次语音识别算法所需的数据量，是否变成一个范围
    int                      silence_tmout;
    int                      silence_start;

    voice_ch_t              *ch;
    voice_ch_io_t           *ops;

    voice_pcm_t             *pcm;
};

#define voice_dcache_writeback csi_dcache_clean_range
#define voice_dcache_invalidate csi_dcache_invalid_range

#define PCM_CACHE_DATA (0x04)

static void voice_entry(void *p);
static void voice_msg_process(void *priv, voice_msg_t *msg);

static void pcm_data_update(voice_t *v, void *data, int len)
{

    if (voice_ops(v)->cache_cts > 0) {
        if (voice_rb_put(v->voice_rb, data, len) != 0) {
            // LOGE(TAG, "voice rb full");
            return;
        }
    } else {
        if (v->pcm_data == NULL) {
            v->pcm_data = aos_malloc_check(len + sizeof(voice_data_t));
        }

        memcpy(v->pcm_data->data, data, len);
        v->pcm_data->len = len;
    }
    
    aos_sem_signal(&v->sem);
}

static void pcm_backflow_update(voice_t *v, void *data, int len)
{
    int temp = *((int*)data);
    int data_id = ((temp) >> 16) & 0xffff;

    v->backflow_enable[data_id] = temp & 0xffff;

    LOGD(TAG, "backflow %s data_id %d\n", v->backflow_enable[data_id] ? "open" : "close", data_id);
}

// static void pcm_update(void *priv, void *data, int len)
// {
//     voice_t *v = (voice_t *)priv;

//     pcm_data_update(v, data, len);
// }

static void voice_cp_start(voice_t *v, void *data, int len)
{
    if (voice_ops(v)->start_alg_tsk) {
        voice_ops(v)->start_alg_tsk(v->priv, voice_entry, v);
    } else {
        aos_task_new("voice", voice_entry, v, 28 * 1024);
    }
    v->task_running = 1;
}

static void voice_param_set(voice_t *v, void *data, int len)
{
    voice_t *ap = (voice_t *)data;

    voice_dcache_invalidate((uint32_t *)ap->mic_param, sizeof(voice_pcm_param_t));
    voice_dcache_invalidate((uint32_t *)ap->ref_param, sizeof(voice_pcm_param_t));

    if (v->mic_param == NULL) {
        v->mic_param = aos_zalloc_check(sizeof(voice_pcm_param_t));
    }
    memcpy(v->mic_param, ap->mic_param, sizeof(voice_pcm_param_t));

    if (ap->ref_param != NULL) {
        if (v->ref_param == NULL) {
            v->ref_param = aos_zalloc_check(sizeof(voice_pcm_param_t));
        }
        memcpy(v->ref_param, ap->ref_param, sizeof(voice_pcm_param_t));
    } else {
        v->ref_param = NULL;
    }

    memcpy(&v->param, &ap->param, sizeof(voice_param_t));
    if (voice_ops(v)->set_param)
        voice_ops(v)->set_param(v->priv, v->param.ai_param);
}

static void voice_wkvc_stat_update(voice_t *v, int is_playing)
{
    LOGD(TAG, "wakeup voice %s", is_playing ? "start" : "stop");
    
    if (voice_ops(v)->wkvc_stat) {
        voice_ops(v)->wkvc_stat(v->priv, is_playing);
    }
}

static void voice_fake_wakeup(voice_t *v)
{
    LOGD(TAG, "fake wakeup");
    v->fake_wakeup = 1;
}

static void voice_ch_init(voice_t *v)
{
    if (v->param.ipc_mode) {
        v->ops = ipc_ch_get();
        v->ch  = v->ops->init(voice_msg_process, v);
    } else {
    }
}

static void voice_msg_process(void *priv, voice_msg_t *msg)
{
    int      cmd = msg->command;
    voice_t *v   = (voice_t *)priv;

    if (cmd != VOICE_PCM_CMD) {
        LOGD(TAG, "receive msg %d", cmd);
    }

    switch (cmd) {
        case VOICE_PCM_CMD:
            if (v->param.ipc_mode == 1) {
                pcm_data_update(v, msg->req_data, msg->req_len);
            }
            break;
        case VOICE_BACKFLOW_CONTROL_CMD:
            pcm_backflow_update(v, msg->req_data, msg->req_len);
            break;
        case VOICE_CP_START_CMD:
            voice_cp_start(v, msg->req_data, msg->req_len);
            break;
        case VOICE_PCM_PARAM_SET_CMD:
            voice_param_set(v, msg->req_data, msg->req_len);
            break;
        case VOICE_WKVC_STAT_CMD: {
            int play_state = *(int *)msg->req_data;
            voice_wkvc_stat_update(v, play_state);
            break;
        }
        case VOICE_FK_WAKEUP_CMD:
            voice_fake_wakeup(v);
            break;        
    }
}

static void voice_ch_deinit(voice_t *v)
{
    
}

static void *pcm_ref_get(voice_t *v, int ms)
{
    void *ref;
    voice_pcm_param_t *p = v->mic_param;

    int offset = p->period_bytes;

    ref = ((char *)v->pcm_data->data + offset);

    return ref;
}

static void *pcm_mic_get(voice_t *v, int ms)
{
    void *mic = NULL;

    if (voice_ops(v)->cache_cts > 0) {
        voice_data_t *ele = voice_rb_get(v->voice_rb);
        if (ele) {
            mic = ele->data;
        }
    } else {
        mic = (void **)v->pcm_data->data;
    }

    return mic;
}

static void voice_evt_send(voice_t *v, int evt_id, void *data, int len)
{
    voice_ai_msg_send(v, evt_id, data, len, 0);
}

static void voice_data_send(voice_t *v, int data_id, void *data, int len, int flag)
{
    if (v->backflow_enable[data_id] != 1) {
        return;
    }
    
    static voice_data_t vdata = {0};
    voice_data_t *p = v->backflow_data[data_id];

    /* if no data, use static data to avoid malloc */
    if (len == 0) {
        p = &vdata;
    } else if (len > 0 && p->len != len) {
        aos_free(p);
        p = aos_zalloc_check(len + sizeof(voice_data_t));
        v->backflow_data[data_id] = p;
    }
    p->type = data_id;
    p->seq++;
    p->flag = flag;
    memcpy(p->data, data, len);
    p->len                    = len;

    voice_ai_msg_send(v, VOICE_DATA_CMD, p, len + sizeof(voice_data_t), 1);
}

static void voice_kwmap_send(voice_t *v, voice_kwmap_t *map)
{
    voice_ai_msg_send(v, VOICE_KW_MAP_CMD, map, sizeof(voice_kwmap_t), 1);
}

static int voice_wait_pcm(voice_t *v, int ms)
{
    return (aos_sem_wait(&v->sem, AOS_WAIT_FOREVER));
}

static void voice_entry(void *p)
{
    int ret;
    voice_t *v = (voice_t *)p;
    int ms = v->param.cts_ms;
    size_t vad_len, kws_len;
    void *mic;
    void *ref;
    char *vad_out;
    char *kws_data;
    voice_kwmap_t *kw_map;
    voice_vad_state vad_state;
    static int vad_cnt = 0;

    if (voice_ops(v)->init)
        voice_ops(v)->init(v->priv);

    if (voice_ops(v)->get_kw_map && voice_ops(v)->get_kw_map(v->priv, &kw_map) == 0) {
        voice_kwmap_send(v, kw_map);
    }

    while (v->task_running) {
        if (voice_wait_pcm(v, ms) == 0) {
            mic = pcm_mic_get(v, ms);
            ref = pcm_ref_get(v, ms);

            if (voice_ops(v)->vad) {
                vad_state = voice_ops(v)->vad(v->priv, mic, ref, ms, (void **)&vad_out, &vad_len);
                if (vad_state != VOICE_VADSTAT_SILENCE || vad_len > 0) {
                    int to_send = 1;
                    /* vad end detected, stop the silence timer */
                    if (vad_state == VOICE_VADSTAT_ASR_FINI) {
                        v->silence_start = 0;
                    }

                    /* control the vad voice event freq to around 1/s */
                    if (vad_state == VOICE_VADSTAT_VOICE) {
                        vad_cnt = (vad_cnt + 1) % (1000 / ms);
                        
                        if (vad_cnt != 0 && vad_len <= 0) {
                            to_send = 0;
                        }
                    }

                    if (to_send) {
                        voice_data_send(v, VOICE_VAD_DATA, vad_out, vad_len, vad_state);
                    }

                } else if (vad_state == VOICE_VADSTAT_SILENCE) {
                    vad_cnt = 0;
                }
                
                if (ret > 0 && v->silence_start) {
                    if ((aos_now_ms() - v->vad_time_stamp) > v->silence_tmout) {
                        voice_evt_send(v, VOICE_SILENCE_CMD, NULL, 0);
                        v->vad_time_stamp = 0;
                        v->silence_start = 0;
                    }
                }
            } else {
                ret = -1;
                vad_out = mic;
            }

            if (voice_ops(v)->kws || v->fake_wakeup) {
                ret = 0;
                if (voice_ops(v)->kws) {
                    ret = voice_ops(v)->kws(v->priv, mic, ref, ms, vad_out);
                }

                if (ret > 0 || v->fake_wakeup) {
                    v->silence_start = 1;
                    v->vad_time_stamp = aos_now_ms();
                    v->fake_wakeup = 0;
                    voice_evt_send(v, VOICE_KWS_CMD, &ret, sizeof(ret));               
                }
            }

            if (voice_ops(v)->get_kws_data && voice_ops(v)->get_kws_data(v->priv, (void **)&kws_data, &kws_len) == 0) {
                voice_data_send(v, VOICE_KWS_DATA, kws_data, kws_len, 0);
            }

            if (voice_ops(v)->asr && voice_ops(v)->asr(v->priv, vad_out, ms) > 0) {
                v->silence_start = 1;
                v->vad_time_stamp = aos_now_ms();
                voice_evt_send(v, VOICE_KWS_CMD, NULL, 0);
            }

            voice_data_send(v, VOICE_MIC_DATA, mic, v->mic_param->period_bytes, 0);
            voice_data_send(v, VOICE_REF_DATA, ref, v->ref_param->period_bytes, 0);
            voice_data_send(v, VOICE_AEC_DATA, vad_out, v->mic_param->period_bytes, 0);
        }
    }

    if (voice_ops(v)->deinit)
        voice_ops(v)->deinit(v->priv);
}

int voice_ai_msg_send(voice_t *v, int cmd, void *data, int len, int sync)
{
    voice_msg_t msg;
    memset(&msg, 0, sizeof(voice_msg_t));
    int ret = -1;

    msg.command = cmd;
    msg.req_data = data;
    msg.req_len = len;
    msg.flag = sync;

    if (v->ops->msg_send)
        ret = (v->ops->msg_send(v->ch, &msg));

    return ret;
}

voice_t *voice_ai_init(void *priv, voice_cts_ops_t *ops)
{
    aos_check_return_null(ops);
    int ret;

    voice_t *v = aos_zalloc_check(sizeof(voice_t));

    voice_ops(v) = ops;
    v->priv   = priv;
    v->silence_tmout = 2000;

    ret       = aos_sem_new(&v->sem, 0);
    v->param.ipc_mode = 1;
    voice_ch_init(v);

    if (ret < 0) {
        aos_free(v);
        return NULL;
    }

    if (voice_ops(v)->cache_cts > 0) {
        v->voice_rb = aos_malloc_check(sizeof(voice_ringbuf_t));
        voice_rb_create(v->voice_rb, voice_ops(v)->cache_cts);
    }

    return v;
}

void voice_ai_deinit(voice_t *v)
{
    aos_check_return(v);

    voice_ch_deinit(v);

    aos_sem_free(&v->sem);

    v->task_running = 0;

    aos_free(v);
}


